package com.hmall.gateway.routers;


import cn.hutool.json.JSONUtil;
import com.alibaba.cloud.nacos.NacosConfigManager;
import com.alibaba.nacos.api.config.listener.Listener;
import com.alibaba.nacos.api.exception.NacosException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.route.RouteDefinition;
import org.springframework.cloud.gateway.route.RouteDefinitionWriter;
import org.springframework.stereotype.Component;
import reactor.core.publisher.Mono;

import javax.annotation.PostConstruct;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executor;


@Component
@Slf4j
@RequiredArgsConstructor
public class DynamicRouteLoader {


    private final NacosConfigManager nacosConfigManager;

    private final RouteDefinitionWriter writer;

    private final String dataId = "gateway-routes.json";

    private final String group = "DEFAULT_GROUP";

    private final Set<String> routeIds = new HashSet<>();

    //这个注解的意思是在项目一启动就执行这个方法
    @PostConstruct
    public void initRouteConfigListener() throws NacosException {
        //项目启动先拉取一下配置，并且添加配置监听器
        String configinfo = nacosConfigManager.getConfigService().getConfigAndSignListener(dataId, group, 5000, new Listener() {
            @Override
            public Executor getExecutor() {
                return null;
            }

            @Override
            public void receiveConfigInfo(String configinfo) {
                //监听到配置变化了，重新加载路由配置
                updateConfig(configinfo);
            }
        });
        // 第一次读取到配置，也需要更新到路由表
        updateConfig(configinfo);
    }


    public void updateConfig(String configInfo) {
        log.info("更新路由配置:{}", configInfo);
        //1.解析配置文件信息， 转为routeDefinition对象
        // 将从 Nacos 获取到的 JSON 格式的路由配置字符串解析为 RouteDefinition 对象列表
        List<RouteDefinition> routeDefinitions = JSONUtil.toList(configInfo, RouteDefinition.class);
        //2.删除旧的路由
        for (String routeId : routeIds){
            writer.delete(Mono.just(routeId)).subscribe();
        }
        // 遍历每一个解析出的 RouteDefinition 对象
        //3.更新路由表
        routeDefinitions.forEach(routeDefinition -> {
            // 使用 writer 将每个路由定义保存到网关的路由表中
            // Mono.just(...) 将当前对象包装成响应式流 Mono，以便异步处理
            // subscribe() 触发实际的保存操作（响应式流默认是惰性的）
            writer.save(Mono.just(routeDefinition)).subscribe();
            //3.2记录路由id，便于下一次更新时删除
            routeIds.add(routeDefinition.getId());
        });


    }
}
